/*
 * jQuery selectable options plugin
 *
 * Copyright 2009 David Sanders
 * Released under the LGPL license
 *
 * Usage:
 *
 *  Apply to a select to make the options editable by double clicking or pressing F2.  Return/blurring
 *  saves the entered text back to the option, escape cancels and reverts the text.  Options may
 *  be navigated in edit mode by using the arrow keys.
 *
 *      $('select').editableOptions();
 *
 */

(function($) {

    $.fn.editableOptions = function(callback) {

        return $(this).each(function() {

            var select = $(this);

            if (!select.data("_editableOptions_dblclick")) {
                // Bind f2 to dblclick
                // We can't edit all of them so only get the first
                select.keyup(function(e) {
                    if (e.keyCode === 113) {
                        select.find('option:selected:first').dblclick();
                    }
                });
                select.data("_editableOptions_dblclick", true);
            }

            select.find('option').each(function() {

                var option = $(this);

                // Guard against multiple applications
                if (option.data("_editableOptions_isEditable")) {
                    return;
                }
                option.data("_editableOptions_isEditable", true);

                option.dblclick(function() {

                    var text = $('<input type="text" />');

                    text.css({
                        "position": "absolute",
                        "padding": 0,
                        "margin": 0,
                        "border": "none",
                        "outline": "none",
                        "font-family": select.css("font-family"),
                        "font-size": select.css("font-size"),
                    });

                    // deal with browsers that don't support option css
                    if (option.width() === 0) {

                        // interpolate the position of the options using the select's height

                        var select_size = select.attr("size");
                        var option_height;

                        // Multi-selects with no size specified will return 0 on webkit
                        if (select_size == 0) {

                            // Guess the size using a test select
                            var test = $('<select size="4">').css({
                                display: "none",
                                fontSize: select.css("font-size")
                            }).appendTo('body');
                            option_height = test.innerHeight() / 4;
                            test.remove();

                        } else {

                            // webkit: anything under 4 is displayed as 4 height
                            if (select_size < 4) {
                                select_size = 4;
                            }
                            option_height = select.innerHeight() / select_size;
                        }

                        text.css({
                            "top": select.position().top +
                                   parseInt(select.css("margin-top")) +
                                   parseInt(select.css("border-top-width")) +
                                   parseInt(select.css("padding-top")) +
                                   select.attr("selectedIndex") * option_height -
                                   select.scrollTop(),
                            "left": select.position().left +
                                    parseInt(select.css("margin-left")) +
                                    parseInt(select.css("border-left-width")) +
                                    parseInt(select.css("padding-left")),
                            // the select scrollbar seems to be 11 pixels wide on webkit
                            "width": select.width() - 11,
                            // set the padding to 1 to make up for the missing border
                            "padding": 1,
                        });

                    } else {

                        var position = option.position();
                        var left_offset = position.left;
                        var top_offset = position.top;

                        if (select.css("position") === "absolute") {
                            var select_position = select.position();
                            left_offset += select_position.left + parseInt(select.css("border-left-width"));
                            top_offset += select_position.top + parseInt(select.css("border-top-width"));
                        }

                        text.css({
                            "left": left_offset,
                            "top": top_offset,
                            "padding-left": option.css("padding-left"),
                            "height": option.height(),
                            "width": option.outerWidth(),
                        });
                    }

                    var save = function(text) {
                        option.text(text.value);
                        $(text).remove();
                        if (callback !== undefined) {
                            // Pass the DOM element to the callback, a query object
                            callback(option.context);
                        }
                    };

                    text.val(option.text()).
                        keyup(function(e) {
                            // return saves
                            if (e.keyCode === 13) {
                                text.blur();
                            // escape cancels
                            } else if (e.keyCode === 27) {
                                text.remove();
                                select.focus();
                            } else if (e.keyCode === 38 || e.keyCode === 40) {
                                // up arrow moves up an option
                                if (e.keyCode === 38) {
                                    sibling = option.prev();
                                // down arrow moves down an option
                                } else if (e.keyCode === 40) {
                                    sibling = option.next();
                                }
                                if (sibling.length > 0) {
                                    option.attr("selected", false); // if multiple
                                    save(this);
                                    if (option_height !== undefined && select.attr("multiple")) {
                                        // turn off multiple to let the select scroll,
                                        // which seems to be an issue with webkit:
                                        // https://bugs.webkit.org/show_bug.cgi?id=31038
                                        select.attr("multiple", false);
                                        sibling.attr("selected", true);
                                        select.attr("multiple", true);
                                    } else {
                                        sibling.attr("selected", true);
                                    }
                                    sibling.dblclick();
                                }
                            }
                        }).
                        blur(function() {
                            save(this);
                            select.focus();
                        }).
                        insertAfter(select).
                        focus().
                        select();

                });

            });

        });

    };

})(jQuery);
